define('framework/dataGrid/DataGridModule', [
    'angular',
    'jquery',
    'framework/datasource/DataSourceModule',
    'framework/clickbox/GillionClickBoxModule'
], function (angular, $) {
    angular.module('DataGridModule', ['DataSourceModule', 'GillionClickBoxModule'])
        .directive('gDataGrid', function ($window, $parse, $compile, $dataSourceManager, Arrays, Functions) {

            var GridProto = Grid.prototype,
                ORDER = {
                    ASC: 'asc',
                    DESC: 'desc'
                };

            function Grid(scope, element, attrs) {
                var me = this;
                me.scope = scope;
                me.element = element;
                me.attrs = attrs;
                me.width = element.width();
                me.hash = scope.$id;
                me.widthStyleBlock = element.children('style').get();

                me.cellValueGetterCache = {};
                me.scopeEventCache = {};
                scope.columns = me.columns = [];
                if (attrs.$columns) {
                    scope.$columns = me.columns;
                }
                me.rowStates = [];
                me.footCellIndex = 0;
                me.requiredSpanColWidthClassName = {};
                me.actions = {};
                me.widthStyleContent = [];

            }

            GridProto.addColumn = function (column) {
                return this.columns.push(column);
            };

            GridProto.getCellValue = function (row, column) {
                var me = this,
                    cellValueGetterCache = me.cellValueGetterCache;
                if (!cellValueGetterCache[column.data]) {
                    cellValueGetterCache[column.data] = me.generateCellValueGetter(column);
                }
                return cellValueGetterCache[column.data](row);
            };

            GridProto.generateCellValueGetter = function (column) {
                if (column.data) {
                    return $parse(column.data);
                }
                return angular.noop;
            };

            GridProto.getSortName = function (sortingIcon) {
                var columnIndex = sortingIcon.closest('th').data('col-index');
                return this.columns[columnIndex].sortable;
            };

            /**
             * @param $event
             * @param $sortingIcon
             */
            GridProto.doSort = function ($event, $sortingIcon) {
                var me = this,
                    scope = me.scope,
                    onBeforeSortColumn = scope.onBeforeSortColumn,
                    onSortColumn = scope.onSortColumn,
                    oldSortDirection = me.sortDirection,
                    sortName = this.getSortName($sortingIcon),
                    direction = $sortingIcon.hasClass('caret-up') ? ORDER.ASC : ORDER.DESC,
                    dataSource = scope.dataSource,
                    sortEventParam;

                if (oldSortDirection === direction) {
                    direction = undefined;
                }
                sortEventParam = {
                    grid: this,
                    sortName: sortName,
                    sortDirection: direction
                };

                if (onBeforeSortColumn(sortEventParam) !== false) {
                    dataSource.sortName = me.sortName = sortName;
                    dataSource.sortDirection = me.sortDirection = direction;
                    onSortColumn(sortEventParam);
                }
            };

            GridProto.isAllRowsChecked = function () {
                return this.scope.source.length === Arrays.count(this.rowStates, function (rowState) {
                        return rowState.checked === true;
                    });
            };

            GridProto.checkboxCellClick = function () {
                var me = this;
                me.scope.allChecked = me.isAllRowsChecked();
                me.resetCheckedRows();
            };


            GridProto.toggleAllChecked = function () {
                var me = this,
                    scope = me.scope;
                angular.forEach(me.rowStates, function (rowStatus) {
                    rowStatus.checked = scope.allChecked;
                });
                me.resetCheckedRows();
            };

            GridProto.resetCheckedRows = function () {
                var me = this,
                    scope = me.scope,
                    checkedRows = scope.checkedRows,
                    source = scope.source;
                checkedRows.length = 0;
                angular.forEach(me.rowStates, function (rowState, index) {
                    if (rowState.checked === true) {
                        checkedRows.push(source[index]);
                    }
                });
                me.updateCheckedFormContrl = true;
            };

            GridProto.checkRowsByOuter = function () {
                var me = this,
                    scope = me.scope,
                    checkedRows = scope.checkedRows,
                    source = scope.source;

                angular.forEach(checkedRows, function (checkedRow) {
                    var recordIndex = Arrays.indexOf(source, checkedRow);
                    me.rowStates[recordIndex].checked = true;
                });
            };

            GridProto.rowClick = function ($event, $row) {
                var me = this,
                    scope = me.scope,
                    rowIndex = $row.data('row-index'),
                    selectedRecord = scope.source[rowIndex],
                    onBeforeSelect = scope.onBeforeSelect,
                    onSelect = scope.onSelect,
                    selectParam = {
                        $event: $event,
                        record: selectedRecord,
                        grid: this,
                        $row: $row
                    };

                if (onBeforeSelect(selectParam) !== false) {
                    scope.selectedRow = selectedRecord;
                    onSelect(selectParam);
                }

            };

            GridProto.cellDbclick = function ($event, $cell) {
                var me = this,
                    columnIndex = $cell.data('cell-index'),
                    column = me.columns[columnIndex],
                    $editor;
                if (column && column.editable) {
                    switch (column.editable) {
                        case 'text':
                            $editor = angular.element('<input role="editor" name="' + column.field + '" ng-model="row[\'' + column.data + '\']" type="text"/>');

                            break;
                    }
                }
                if ($editor){
                    if (me.scope.validatorName) {
                        $editor.data('$formController', me.formController) ;
                        $editor.data('$gValidatorController', me.gValidatorController);
                    }
                    $editor.attr('g-field-validator', '');
                    $compile($editor)($cell.scope());
                    $cell.empty().append($editor);
                }
            };

            GridProto.finishEdit = function ($event) {
                var me = this,
                    target = $event.target || $event.srcElement,
                    $target = angular.element(target),
                    $editingCells = angular.element('td:has([role=editor])');
                if ($editingCells.length > 0 && $target.attr('role') !== 'editor') {
                    $editingCells.empty();
                    $editingCells.each(function () {
                        //noinspection JSUnresolvedVariable
                        var $editingCell = angular.element(this),
                            cellScope = $editingCell.scope(),
                            row = cellScope.row,
                            column = cellScope.column,
                            tmpl = column.tmpl,
                            html;
                        if (tmpl) {
                            $editingCell.append($compile(tmpl)(cellScope));
                        } else {
                            html = me.getCellValue(row, column);
                            $editingCell.html(html);
                        }
                    });

                }
            };

            /**
             * 子指令scope定义方法属性时， 指定到grid外scope的某个方法， 通过本方法获取
             * @param eventDefineAttrVal 方法属性指定的值
             * @return {*}
             */
            GridProto.getScopeEvent = function (eventDefineAttrVal) {
                var me = this,
                    scopeEventCache = me.scopeEventCache,
                    gridOuterScope = me.scope.$parent;
                if (!!eventDefineAttrVal) {
                    if (!scopeEventCache[eventDefineAttrVal]) {
                        var parentGet = $parse(eventDefineAttrVal);
                        return function (locals) {
                            return parentGet(gridOuterScope, locals);
                        };
                    }
                    return scopeEventCache[eventDefineAttrVal];
                }
                return angular.noop;
            };

            GridProto.setCellsWidth = function () {
                var me = this,
                    columns = me.columns,
                    gridWidth = me.element.width(),
                    width = gridWidth - 19, // 减去 space cell width
                    widthStyleContent = '.' + me.getColWidthClassName(0) + '{width:40px;}',
                    undefinedWidthColIndexes = [],
                    colWidths = me.colWidths = [],
                    colWidth, undefinedColWidth, colRealIndex, extraWidth;
                if (me.hasCheckbox) {
                    width -= 40;
                    colWidths.push(40);
                }
                extraWidth = width;

                angular.forEach(columns, function (column, index) {
                    colRealIndex = me.hasCheckbox ? (index + 1) : index;
                    colWidth = me.getCellWidth(column, width);
                    if (colWidth) {
                        colWidths[colRealIndex] = colWidth;
                        widthStyleContent += ('.grid-' + me.hash + '-col-' + colRealIndex + '{width:' + colWidth + 'px;}');
                        extraWidth -= colWidth;
                    } else {
                        undefinedWidthColIndexes.push(index);
                    }
                });
                undefinedColWidth = Math.floor(extraWidth / undefinedWidthColIndexes.length);
                if (undefinedColWidth <= 0) {
                    undefinedColWidth = 50;
                }
                angular.forEach(undefinedWidthColIndexes, function (undefinedWidthColIndex) {
                    colRealIndex = me.hasCheckbox ? (undefinedWidthColIndex + 1) : undefinedWidthColIndex;
                    colWidths[colRealIndex] = undefinedColWidth;
                    widthStyleContent += ('.grid-' + me.hash + '-col-' + colRealIndex + '{width:' + undefinedColWidth + 'px;}');
                });

                angular.forEach(me.requiredSpanColWidthClassName, function (startAndEnd, className) {
                    var startColIndex = startAndEnd[0],
                        endColIndex = startAndEnd[1],
                        betweenColWidths = colWidths.slice(startColIndex, endColIndex),
                        spanColWidth = Arrays.reduce(betweenColWidths, Functions.sum);
                    widthStyleContent += ('.' + className + '{width:' + spanColWidth + 'px;}');
                });
                console.log(widthStyleContent);
                angular.element('<style type="text/css">' + widthStyleContent + '</style>').appendTo(me.element);
            };

            GridProto.getCellWidth = function (column, dataCellWidth) {
                var width = column.width,
                    widthPercent;
                if (!!width) {
                    if (/^\d+%$/.test(width)) {
                        widthPercent = Number(width.substring(0, width.length - 1));
                        return dataCellWidth * widthPercent / 100;
                    } else if (/^\d+px$/.test(width)) {
                        return Number(width.substring(0, width.length - 2));
                    } else if (/^\d+$/.test(width)) {
                        return Number(width);
                    }
                }
            };

            GridProto.registerFootCellWidthClass = function (span) {
                var me = this,
                    startColIndex = me.footCellIndex,
                    colspan = isNaN(span) ? 1 : span,
                    endColIndex = startColIndex + colspan,
                    colWidthClassName = me.getColWidthClassName(startColIndex);
                if (colspan > 1) {
                    colWidthClassName += ('-' + endColIndex);
                    me.requiredSpanColWidthClassName[colWidthClassName] = [startColIndex, endColIndex];
                }
                me.footCellIndex += colspan;
                return colWidthClassName;
            };

            /**
             *
             * @param startColIndex {Number} 开始的`column`的`index`
             * @param endColIndex {Number} 结束
             * @return {string}
             */
            /*GridProto.getSpanColWidthClassName = function (startColIndex, endColIndex) {
                return spanColWidthClassName = 'grid-' + this.hash + '-col-' + startColIndex;
                if (colspan > 1) {
                    return spanColWidthClassName + '-' + (startColIndex + colspan);
                }
                return spanColWidthClassName;
            };*/

            /**
             *
             * @param startColIndex {Number} 开始的`column`的`index`
             * @return {string}
             */
            GridProto.getColWidthClassName = function (startColIndex) {
                return 'grid-' + this.hash + '-col-' + startColIndex;
            };

            return {
                template: ' <div class="grid"> <div class="grid-head" ng-click="headClick($event)"> <table class="table-head"> <tbody> <tr> <th class="table-scroll-space"></th> </tr> </thead> </table> </div> <div class="grid-body" ng-click="bodyClick($event)" ng-dblclick="bodyDbclick($event)"> <table class="table-body"> <tbody> <tr ng-repeat="(rowIndex, row) in source" active="{{selectedRow === row}}" data-row-index="{{rowIndex}}"> <td class="grid-col-checkbox" ng-if="grid.hasCheckbox" data-checkbox-cell="true"> <g-checkbox ng-model="grid.rowStates[rowIndex].checked"></g-checkbox> </td> <td g-cell ng-repeat="(columnIndex, column) in columns"  data-row-index="{{rowIndex}}" data-cell-index="{{columnIndex}}">&nbsp;</td> </tr> </tbody> </table> </div> </div> ',
                //templateUrl: window.ctx + '/static/app/framework/dataGrid/dataGrid.html',
                replace: true,
                restrict: 'E',
                transclude: true,
                scope: {
                    sourceName: '@sourceName',
                    validatorName: '@',
                    $source: '=source',
                    $columns: '=columns',
                    $selectedRow: '=selectedRow',
                    $checkedRows: '=checkedRows',
                    onLoadSuccess: '&',
                    onRender: '&',
                    onBeforeSortColumn: '&',
                    onSortColumn: '&',
                    onBeforeSelect: '&',
                    onSelect: '&'
                },
                require: 'gDataGrid',
                compile: function (tElement, tAttributes, transclude) {
                    if (!!tAttributes.validatorName) {
                        var validatorName = tAttributes.validatorName,
                            bodyTable = tElement.find('.table-body');
                        bodyTable.wrap('<form g-validator="' + validatorName + '" name="gridForm{{$id}}"></form>')
                    }
                    return function (scope, element, attrs, grid) {
                        var $head = element.find('.grid-head'),
                            $body = element.find('.grid-body'),
                            $headTableBody = $head.find('tbody'),
                            rowStates = grid.rowStates,
                            isBindCheckedRows = attrs.hasOwnProperty('checkedRows'),
                            isBindSelectedRow = attrs.hasOwnProperty('selectedRow');

                        if (scope.validatorName) {
                            var form = $compile('<form name="grid' + grid.hash + 'form" g-validator="' + scope.validatorName + '" data-invalid-msg="tooltip"></form>')(scope);
                            grid.formController = form.data('$formController');
                            grid.gValidatorController = form.data('$gValidatorController');
                            form.wrap($body);
                        }

                        $dataSourceManager.getDataSource(attrs.sourceName).then(function (result) {
                            scope.dataSource = result;
                        });
                        scope.$on(scope.sourceName, function (event, result) {
                            scope.source = result['records'];
                            if (result.hasOwnProperty('moreAttrs')) {
                                scope.moreAttrs = result['moreAttrs'];
                            }

                            // clear rows state
                            scope.allChecked = false;
                            rowStates.length = 0;
                            //noinspection JSUnresolvedVariable
                            Arrays.doForAll(result.records, function () {
                                rowStates.push({});
                            });
                            if (angular.isFunction(scope.onLoadSuccess)) {
                                scope.onLoadSuccess({
                                    source: scope.source,
                                    grid: grid,
                                    result: result
                                });
                            }
                        });

                        transclude(scope, function (clone) {
                            var headCells = clone.filter('th'),
                                $headSpaceCol = $headTableBody.find('tr th.table-scroll-space'),
                                $foot = clone.filter('.grid-foot');
                            if (headCells.length > 0) {
                                angular.forEach(headCells, function (cell) {
                                    $headSpaceCol.before(cell);
                                });
                            }
                            if ($foot.length > 0) {
                                // init footData
                                grid.footCells = [];
                                //gridController.footColGroup = foot.find('colgroup');
                                element.append($foot);
                                // 绑定 scroll
                                $body.bind('scroll', function (event) {
                                    var scrollLeft = (event.target || event.srcElement).scrollLeft;
                                    $foot[0].scrollLeft = scrollLeft;
                                });
                            }
                        }, grid);

                        scope.headClick = function ($event) {
                            var target = $event.target || $event.srcElement,
                                $target = angular.element(target),
                                $parent;
                            if ($target.is('span.caret')) {
                                grid.doSort($event, $target);
                            }
                            if ($target.is('i')) {
                                $parent = $target.parent();
                                if ($parent.hasClass('caret')) {
                                }
                                if ($target.hasClass('fi')) {
                                    grid.toggleAllChecked();
                                }
                            }
                        };

                        scope.bodyClick = function ($event) {
                            var target = $event.target || $event.srcElement,
                                $target = angular.element(target),
                                $cell = $target.closest('td'),
                                $row = $cell.closest('tr');
                            if ($target.is('i.fi') && $target.closest('td').data('checkbox-cell') === true) {
                                grid.checkboxCellClick();
                            }
                            grid.rowClick($event, $row);
                        };

                        $($window).on('click', function (event) {
                            grid.finishEdit((event))
                        });

                        scope.bodyDbclick = function ($event) {
                            var target = $event.target || $event.srcElement,
                                $target = angular.element(target),
                                $cell = $target.closest('td'),
                                $row = $cell.closest('tr');
                            grid.cellDbclick($event, $cell, $row);
                        };


                        // 实现 grid选中行双向绑定 start
                        scope.$watch('selectedRow', function (newRow, oldRow) {
                            var $allRows,recordIndex, $row;
                            if (newRow !== oldRow) {
                                $allRows = element.find('.grid-body tr');
                                recordIndex = Arrays.indexOf(scope.source, newRow);
                                if (isBindSelectedRow) {
                                    scope.$selectedRow = newRow;
                                }
                            }
                        });

                        if (isBindSelectedRow) {
                            scope.$watch('$selectedRow', function (newRow, oldRow) {
                                var $allRows = element.find('.scroll-table-mian tr'),
                                    recordIndex, $row;
                                if (newRow !== oldRow && scope.selectedRow !== newRow) {
                                    recordIndex = Arrays.indexOf(scope.source, newRow);
                                    $row = angular.element($allRows.get(recordIndex));
                                    grid.rowClick({}, $row);
                                }
                            });
                        }

                        if (isBindCheckedRows) {
                            if (angular.isArray(scope.$checkedRows)) {
                                scope.checkedRows = scope.$checkedRows;
                            } else {
                                scope.checkedRows = scope.$checkedRows = [];
                            }
                            scope.$watchCollection('$checkedRows', function (newCheckedRows) {
                                if (grid.updateCheckedFormContrl !== true) {
                                    grid.checkRowsByOuter();
                                }
                                grid.updateCheckedFormContrl = false;
                            });
                        } else {
                            scope.checkedRows = [];
                        }

                        // 实现 grid选中行双向绑定 end

                        // 表格宽度js设定 start
                        grid.setCellsWidth();
                        // 表格宽度js设定 edn

                        if (angular.isFunction(scope.onRender)) {
                            scope.onRender({
                                grid: grid,
                                source: scope.source
                            });
                        }

                        // 绑定 scroll
                        $body.bind('scroll', function (event) {
                            $head[0].scrollLeft = (event.target || event.srcElement).scrollLeft;
                        });

                        $($window).resize(function(){
                            grid.setCellsWidth();
                        });
                    };
                },
                controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
                    $scope.grid = new Grid($scope, $element, $attrs);
                    return $scope.grid;
                }]
            };
        })
        .directive('gCheckboxColumn', function () {
            return {
                restrict: 'E',
                template: '<th class="grid-col-checkbox"><g-checkbox ng-model="allChecked"></g-checkbox></th>',
                replace: true,
                scope: false,
                require: '^gDataGrid',
                link: function (scope, element, attrs, gridController) {
                    gridController.hasCheckbox = true;
                }
            };
        })
        .directive('gColumn', function () {
            return {
                template: '<th> <div class="grid-head-sort" ng-if="$sortable !== undefined"> <button class="btn"><span ng-class="{\'selected\': grid.sortName === sortable && grid.sortDirection === \'asc\'}" class="caret caret-up"></span></button> <button class="btn"><span ng-class="{\'selected\': grid.sortName === sortable && grid.sortDirection === \'desc\'}" class="caret caret-down"></span></button> </div> </th>',
                replace: true,
                restrict: 'E',
                transclude: true,
                scope: {
                    $sortable: '@sortable',
                    data: '@',
                    width: '@',
                    editable: '@',
                    tmpl: '@'
                },
                require: '^gDataGrid',
                compile: function (tElement, tAttrs, transclude) {
                    return function (scope, element, attrs, gridController) {
                        transclude(scope, function (clone) {
                            clone.not('g-column-tmpl').appendTo(element);
                        }, gridController);
                        var columnInstance = {
                                field: scope.data,
                                data: scope.data,
                                hash: scope.$id,
                                width: scope.width,
                                editable: scope.editable,
                                tmpl: scope.tmpl,
                                element: element
                            },
                            newLength = gridController.addColumn(columnInstance),
                            colIndex = newLength - 1,
                            realColIndex = gridController.hasCheckbox ? (colIndex + 1) : colIndex,
                            sortable;

                        // 如果需要排序， data中有空格视为包含过滤器， 截取空格前字符串作为排序字段
                        if (attrs.hasOwnProperty('sortable')) {
                            sortable = scope.data;
                            if (/\s/.test(sortable)) {
                                columnInstance.field = sortable = sortable.split(' ')[0];
                            }
                        }

                        columnInstance.columnWidthClass = 'grid-' + gridController.hash + '-col-' + realColIndex;
                        columnInstance.index = colIndex;
                        columnInstance.sortable = scope.sortable = sortable;

                        scope.grid = gridController;

                        element.data('col-index', colIndex);
                        attrs.$addClass(columnInstance.columnWidthClass);
                    };
                }
            };
        })
        .directive('gCell', function ($compile) {
            return {
                restrict: 'A',
                scope: false,
                require: '^gDataGrid',
                link: function (scope, element, attrs, gridController) {
                    //noinspection JSUnresolvedVariable
                    var row = scope.row,
                        column = scope.column,
                        tmpl = column.tmpl,
                        colIndex = gridController.hasCheckbox ? (column.index + 1) : column.index,
                        html;
                    if (tmpl) {
                        element.append($compile(tmpl)(scope));
                    } else {
                        html = gridController.getCellValue(row, column);
                        element.html(html);
                    }
                    attrs.$addClass('grid-' + gridController.hash + '-col-' + colIndex);
                }
            };
        })
        .directive('gFooter', function () {
            return {
                restrict: 'E',
                template: '<div class="grid-foot"> <table class="table-foot"> <tbody> <tr> <th class="table-scroll-space"></th> </tr> </tbody> </table> </div>',
                replace: true,
                transclude: true,
                scope: false,
                require: '^gDataGrid',
                compile: function (tElement, tAttrs, transclude) {
                    return function (scope, element, attrs, gridController) {
                        var $footSpaceCell = element.find('.table-scroll-space');
                        transclude(scope, function (clone) {
                            var footCells = clone.filter('th');
                            $footSpaceCell.before(footCells);
                        }, gridController);
                    }
                }
            };
        })
        .directive('gFooterCell', function () {
            return {
                restrict: 'E',
                template: '<th><div ng-transclude/> </th>',
                replace: true,
                transclude: true,
                scope: false,
                require: '^gDataGrid',
                link: function (scope, element, attrs, gridController) {
                    var span = Number(element.attr('colspan')),
                        spanColWidthClassName = gridController.registerFootCellWidthClass(span);

                    attrs.$addClass(spanColWidthClassName);
                }
            };
        })
        // just for stored column's template
        .directive('gColumnTmpl', function () {
            return {
                restrict: 'E',
                scope: false,
                compile: function (tElement) {
                    var columnTmpl = $.trim(tElement.html());
                    return function (scope) {
                        scope.tmpl = columnTmpl;
                    }
                }
            };
        })
        // NOTE 一个表格中只能定义一个删除按钮
        .directive('gActionRemove', function () {
            return {
                restrict: 'E',
                template: '<a href="javascript:;" ng-click="removeRecord($event)" class="btn-set" title="删除"><i class="btn-set-icon iconfont icon-shanchu"></i></a>',
                scope: false,
                link: function (scope, element, attrs) {
                    var grid = scope.grid,
                        onBeforeActionAttrVal = attrs['onBeforeAction'],
                        onActionAttrVal = attrs['onAction'],
                        onBeforeAction = angular.noop,
                        onAction = angular.noop;
                    if (grid) {
                        if (!!onBeforeActionAttrVal) {
                            onBeforeAction = grid.getScopeEvent(onBeforeActionAttrVal);
                        }
                        if (!!onActionAttrVal) {
                            onAction = grid.getScopeEvent(onActionAttrVal);
                        }
                    }

                    scope.removeRecord = function ($event) {
                        //noinspection JSUnresolvedVariable
                        var eventParams = {
                            $event: $event,
                            record: scope.row
                        };
                        if (onBeforeAction(eventParams) !== false) {
                            grid.scope.source.splice(scope.rowIndex, 1);
                            onAction(eventParams);
                        }
                    }
                }
            };
        })
        .directive('gGridEditor', function () {
            return {

            }
        });
});